﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace library
{
    static class LocalPackets
    {
        internal delegate void PacketArrivedHandler(byte[] address, byte[] data);

        internal static event PacketArrivedHandler OnPacketArrived;

        internal static ManualResetEvent localPacketEvent = new ManualResetEvent(true);

        static List<byte[]> addresses = new List<byte[]>();

        static Stream addressesFile;

        static void Load()
        {
            if (!Directory.Exists(Parameters.localPacketsDir))
                Directory.CreateDirectory(Parameters.localPacketsDir);

            if (!File.Exists(Parameters.localPacketsFile))
            {
                addressesFile = new FileStream(Parameters.localPacketsFile, FileMode.Create, FileAccess.ReadWrite);

                string[] files = Directory.GetFiles(Parameters.localPacketsDir);

                if (files.Length == 0)
                    return;

                foreach (var file in files)
                {
                    byte[] buffer = Encoding.Unicode.GetBytes(file);

                    addressesFile.Write(buffer, 0, buffer.Length);
                }
            }

            var data = File.ReadAllBytes(Parameters.localPacketsFile);

            addressesFile = new FileStream(Parameters.localPacketsFile, FileMode.Create, FileAccess.ReadWrite);

            var offset = 0;

            var emptyAddress = new byte[Parameters.addressSize];

            while (offset < data.Length)
            {
                var buffer = data.Skip(offset).Take(Parameters.addressSize).ToArray();

                offset += Parameters.addressSize;

                if (!Addresses.Equals(emptyAddress, buffer))
                    AddAddress(buffer);
            }
        }

        static void Remove(byte[] address)
        {
            lock (addresses)
            {
                var indexOf = addresses.IndexOf(address);

                addresses[indexOf] = null;

                addressesFile.Seek(indexOf * Parameters.addressSize, 0);

                var buffer = new byte[Parameters.addressSize];



                addressesFile.Read(buffer, 0, buffer.Length);

                if (!Addresses.Equals(buffer, address))
                {
                }

                addressesFile.Write(new byte[Parameters.addressSize], 0, Parameters.addressSize);

                //addresses.RemoveAt(indexOf);
            }

            string path = Path.Combine(Parameters.localPacketsDir, Utils.ToBase64String(address));

            if (File.Exists(path))
                File.Delete(path);
        }

        static void AddAddress(byte[] address)
        {
            lock (addresses)
                addresses.Add(address);

            addressesFile.Seek(addressesFile.Length, 0);

            addressesFile.Write(address, 0, Parameters.addressSize);

            localPacketEvent.Set();
        }

        internal static void Add(byte[] address, byte[] data, Peer peer)
        {
            if (!peer.Equals(Client.LocalPeer))
            {
                byte[] actual = Exists(address);

                VerifyIntegrity(address, actual, data, peer);

                if (actual != null)
                    return;
            }

            DelayedWrite.Add(Path.Combine(Parameters.localPacketsDir, Utils.ToBase64String(address)), data);

            AddAddress(address);

            var packetType = (PacketTypes)data[0];

            if (packetType == PacketTypes.Post && peer == Client.LocalPeer)
            {
                //LocalIndex. AddAddress();    
            }

            if (OnPacketArrived != null)
                OnPacketArrived(address, data);
        }

        internal static byte[] Get(byte[] address)
        {
            string filename = Path.Combine(Parameters.localPacketsDir, Utils.ToBase64String(address));

            byte[] data = DelayedWrite.Get(filename);

            if (data != null)
                return data;
            try
            {
                if (File.Exists(filename))
                    return File.ReadAllBytes(filename);
            }
            catch { }

            return null;
        }

        internal static IEnumerable<byte[]> GetByPacketType(PacketTypes type)
        {
            foreach (var address in addresses)
            {
                var data = Get(address);

                if (data[0] == (byte) type)
                    yield return address;
            }
        }

        internal static byte[] Exists(byte[] address)
        {
            for (int i = 0; i < addresses.Count(); i++)
            {
                byte[] b = addresses[i];

                if (Addresses.Equals(b, address))
                    return b;
            }

            return null;
        }

        static void VerifyIntegrity(byte[] address, byte[] actual, byte[] data, Peer peer)
        {
            //todo:
        }

        #region Thread Refresh

        internal static void Start()
        {
            Load();

            Thread thread = new Thread(Refresh);

            // thread.Start();
        }

        internal static void Stop()
        {
            localPacketEvent.Set();

            Peers.idlePeerEvent.Set();

            Client.Stats.belowMinSentEvent.Set();

            addressesFile.Close();
        }

        static void Refresh()
        {
            while (!Client.Stop)
            {
                bool any;

                lock (addresses)
                    any = addresses.Any();

                if (!any)
                {
                    localPacketEvent.Reset();

                    localPacketEvent.WaitOne();
                }

                if (Client.Stop)
                    break;

                if (!queue.Any())
                {
                    FillQueue();
                }

                byte[] address = queue.Dequeue();

                Client.Stats.belowMinSentEvent.WaitOne();  //todo: or max confomr % de uso, ver outro uso

                if (Client.Stop)
                    break;

                p2pRequest.Send(
                    address: address,
                    data: Get(address));
            }
        }

        #endregion

        #region Queue

        static Queue<byte[]> queue = new Queue<byte[]>();

        static void Dequeue()
        {


            //todo: mas nao todo agora
            //if (addresses.Count() > Parameters.maxDataAddress)
            //{
            //    lock (addresses)
            //        address =
            //            addresses.OrderBy(x => Parameters.addressSize - Addresses.Compare(Client.LocalPeer.Address, x))
            //                .Last();
            //
            //    Remove(address);
            //}
        }

        static void FillQueue()
        {
            lock (addresses)
                queue = new Queue<byte[]>(addresses.OrderBy(x => Parameters.addressSize - Addresses.Compare(Client.LocalPeer.Address, x)));
        }

        #endregion

    }

    enum PacketTypes
    {
        Content = 1,
        Addresses = 2,
        Directory = 4,
        Post = 8,
        Index = 16
    }

}
